package mongotesting

import (
	"context"
	"fmt"
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/client"
	"github.com/docker/go-connections/nat"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
	"testing"
)

var (
	image           = "mongo:latest"
	mongoURI        = ""
	defaultMongoURI = "mongodb://localhost:27017/?readPreference=primary&ssl=false"
)

// RunWithMongoInDocker runs the tests with a mongodb instance in a docker container.
func RunWithMongoInDocker(m *testing.M) int {
	ctx := context.Background()
	cli, err := client.NewClientWithOpts(client.FromEnv)
	if err != nil {
		panic(err)
	}

	resp, err := cli.ContainerCreate(ctx, &container.Config{
		Image: image,
		ExposedPorts: nat.PortSet{
			"27017/tcp": {},
		},
		Tty: false,
	}, &container.HostConfig{
		PortBindings: nat.PortMap{
			"27017/tcp": []nat.PortBinding{
				{
					HostIP:   "127.0.0.1",
					HostPort: "0",
				},
			},
		},
	}, nil, nil, "mongo_test_1")
	if err != nil {
		panic(err)
	}

	containerId := resp.ID

	defer func() {
		fmt.Println("container stop...")
		if err = cli.ContainerRemove(ctx, containerId, types.ContainerRemoveOptions{
			Force: true,
		}); err != nil {
			panic(err)
		}
	}()

	if err := cli.ContainerStart(ctx, containerId, types.ContainerStartOptions{}); err != nil {
		panic(err)
	}

	inspect, err := cli.ContainerInspect(ctx, containerId)
	if err != nil {
		panic(err)
	}

	ins := inspect.NetworkSettings.Ports["27017/tcp"][0]

	fmt.Printf("container start at port: %+v\n", ins.HostPort)

	mongoURI = fmt.Sprintf("mongodb://%s:%s/?readPreference=primary&ssl=false", ins.HostIP, ins.HostPort)

	return m.Run()
}

// NewClient creates a client connected to the mongo instance.
func NewClient(ctx context.Context) (*mongo.Client, error) {
	if mongoURI == "" {
		return nil, fmt.Errorf("mongouri not set.please run RunWithMongoInDocker in testMain")
	}
	return mongo.Connect(ctx, options.Client().ApplyURI(mongoURI))
}

// NewDefaultClient creates a default client connected to the mongo instance.
func NewDefaultClient(ctx context.Context) (*mongo.Client, error) {
	return mongo.Connect(ctx, options.Client().ApplyURI(defaultMongoURI))
}

// SetupIndexes sets up indexes for the given database.
func SetupIndexes(c context.Context, d *mongo.Database) error {
	_, err := d.Collection("account").Indexes().CreateOne(c, mongo.IndexModel{
		Keys: bson.D{
			{Key: "open_id", Value: 1},
		},
		Options: options.Index().SetUnique(true),
	})
	if err != nil {
		return err
	}

	_, err = d.Collection("trip").Indexes().CreateOne(c, mongo.IndexModel{
		Keys: bson.D{
			{Key: "trip.accountid", Value: 1},
			{Key: "trip.status", Value: 1},
		},
		Options: options.Index().SetUnique(true).SetPartialFilterExpression(bson.M{
			"trip.status": 1,
		}),
	})
	if err != nil {
		return err
	}

	_, err = d.Collection("profile").Indexes().CreateOne(c, mongo.IndexModel{
		Keys: bson.D{
			{Key: "accountid", Value: 1},
		},
		Options: options.Index().SetUnique(true),
	})
	return err

}
